package com.swak.frame.mybatis.spring.configuration;

import java.util.List;
import java.util.Map;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.util.CollectionUtils;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.swak.frame.SwakConstants;
import com.swak.frame.mybatis.DynamicMapperScanAspect;
import com.swak.frame.mybatis.ReadWriteSqlSessionTemplate;
import com.swak.frame.mybatis.config.SwakDataBasePropertie;
import com.swak.frame.mybatis.constants.SwakDataBaseConstants;
import com.swak.frame.mybatis.ydd.DynamicDataSource;
import com.swak.frame.mybatis.ydd.DynamicRoutingDataSource;
import com.swak.frame.mybatis.ydd.util.config.YddAppConfig;
import com.swak.frame.registry.SwakBeanDefinitionRegistry;
import com.swak.frame.system.SystemConfigurable;

//@Configuration
//@PropertySource(value = {"${swak.frame.datasource.filePath:}"}, ignoreResourceNotFound = true)
//@EnableConfigurationProperties(SwakDataBasePropertie.class)
//@ConditionalOnProperty(prefix = SwakConstants.SWAK_DATA_SOURCE, name = "enabled", havingValue = "true",
//    matchIfMissing = false)
public class SwakDataBaseAutoConfiguration extends SwakBeanDefinitionRegistry {
    // private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private SystemConfigurable systemConfig;

    @Bean(SwakDataBaseConstants.YDDAPP_CONFIG)
    public YddAppConfig yddAppConfig() {
        System.out.println(systemConfig.getString("swak.frame.datasource.scanPackages", null));
        YddAppConfig yddAppConfig = new YddAppConfig();
        yddAppConfig.setAppId(systemConfig.getBizName());
        return yddAppConfig;
    }

    @Bean(SwakDataBaseConstants.READ_WRITE_SQLSESSION_TEMPLATE)
    public ReadWriteSqlSessionTemplate readWriteSqlSessionTemplate(final SwakDataBasePropertie swakDataBasePropertie)
        throws BeansException, Exception {
        Map<String, SqlSessionFactory> targetSqlSessionFactorys = getSqlSessionFactory(swakDataBasePropertie);
        SqlSessionFactory writeSqlSessionFactory = targetSqlSessionFactorys.get("write");
        ReadWriteSqlSessionTemplate readWriteSqlSessionTemplate =
            new ReadWriteSqlSessionTemplate(writeSqlSessionFactory);
        readWriteSqlSessionTemplate.setTargetSqlSessionFactorys(targetSqlSessionFactorys);
        return readWriteSqlSessionTemplate;
    }

    // 注册mysqlJdbcTemplate
    private Map<String, SqlSessionFactory> getSqlSessionFactory(final SwakDataBasePropertie swakDataBasePropertie)
        throws BeansException, Exception {
        Map<String, DynamicRoutingDataSource> allRoutingDataSource = getDynamicRoutingDataSource(swakDataBasePropertie);
        // 注册writeSqlSessionFactory
        Map<String, Object> writeSqlSessionFactory = Maps.newHashMap();
        writeSqlSessionFactory.put("dataSource", allRoutingDataSource.get("write"));
        writeSqlSessionFactory.put("configLocation", swakDataBasePropertie.getConfigLocation());
        writeSqlSessionFactory.put("mapperLocations", swakDataBasePropertie.getMapperLocations());
        registerIfNotExists(SwakDataBaseConstants.WRITE_SQLSESSIONFACTORY, SqlSessionFactoryBean.class,
            writeSqlSessionFactory);

        // 注册readSqlSessionFactory
        Map<String, Object> readSqlSessionFactory = Maps.newHashMap();
        readSqlSessionFactory.put("dataSource", allRoutingDataSource.get("read"));
        readSqlSessionFactory.put("configLocation", swakDataBasePropertie.getConfigLocation());
        readSqlSessionFactory.put("mapperLocations", swakDataBasePropertie.getMapperLocations());
        registerIfNotExists(SwakDataBaseConstants.READ_SQLSESSIONFACTORY, SqlSessionFactoryBean.class,
            readSqlSessionFactory);

        Map<String, SqlSessionFactory> sqlSessionFactorys = ImmutableMap.of("write",
            beanFactory.getBean(SwakDataBaseConstants.WRITE_SQLSESSIONFACTORY, SqlSessionFactory.class), "read",
            beanFactory.getBean(SwakDataBaseConstants.READ_SQLSESSIONFACTORY, SqlSessionFactory.class));

        return sqlSessionFactorys;
    }

    private Map<String, DynamicRoutingDataSource> getDynamicRoutingDataSource(
        final SwakDataBasePropertie swakDataBasePropertie) {
        YddAppConfig yddAppConfig = beanFactory.getBean(YddAppConfig.class);
        DynamicDataSource masterDataSource =
            buildDynamicDatabase(swakDataBasePropertie.getMaster(), SwakDataBaseConstants.MASTER, yddAppConfig);
        // 注入备库
        List<String> slaves = swakDataBasePropertie.getSlaves();
        Map<String, DynamicDataSource> slavesDataSource = Maps.newHashMap();
        //slaves为空的情况,默认未master
        if (CollectionUtils.isEmpty(slaves)) {
            slavesDataSource.put("mysql_dataSoruce_slave0", masterDataSource);
        } else {
            for (int i = 0; i < slaves.size(); i++) {
                String slavesBeanName = "mysql_dataSoruce_slave" + i;
                DynamicDataSource slaveDataSource = buildDynamicDatabase(slaves.get(i), slavesBeanName, yddAppConfig);
                slavesDataSource.put(slavesBeanName, slaveDataSource);
            }
        }
        
        // DynamicRoutingDataSource
        DynamicRoutingDataSource dataSourceRead =
            buildDynamicRoutingDatabase(SwakDataBaseConstants.ROUTING_READ, slavesDataSource, masterDataSource);
        DynamicRoutingDataSource dataSourceWrite = buildDynamicRoutingDatabase(SwakDataBaseConstants.ROUTING_WRITE,
            ImmutableMap.of(SwakDataBaseConstants.MASTER, masterDataSource),
            slavesDataSource.values().iterator().next());

        // 注入JdbcTemplate
        registerIfNotExists("mysqlJdbcTemplate", JdbcTemplate.class, ImmutableMap.of("dataSource", masterDataSource));

        return ImmutableMap.of("write", dataSourceWrite, "read", dataSourceRead);
    }

    private DynamicRoutingDataSource buildDynamicRoutingDatabase(String beanName,
        Map<String, DynamicDataSource> targetDataSources, DynamicDataSource defaultTargetDataSource) {
        if (beanFactory.containsBeanDefinition(beanName)) {
            return beanFactory.getBean(beanName, DynamicRoutingDataSource.class);
        }
        BeanDefinitionBuilder beanDefinitionBuilder =
            BeanDefinitionBuilder.genericBeanDefinition(DynamicRoutingDataSource.class);
        beanDefinitionBuilder.addPropertyValue("targetDataSources", targetDataSources);
        beanDefinitionBuilder.addPropertyValue("defaultTargetDataSource", defaultTargetDataSource);
        BeanDefinition lastBeanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        beanFactory.registerBeanDefinition(beanName, lastBeanDefinition);
        return beanFactory.getBean(beanName, DynamicRoutingDataSource.class);
    }

    private DynamicDataSource buildDynamicDatabase(String configFile, String beanName, YddAppConfig yddAppConfig) {
        BeanDefinitionBuilder beanDefinitionBuilder =
            BeanDefinitionBuilder.genericBeanDefinition(DynamicDataSource.class);
        beanDefinitionBuilder.addPropertyValue("yddAppConfig", yddAppConfig);
        beanDefinitionBuilder.addPropertyValue("poolId", beanName);
        beanDefinitionBuilder.addPropertyValue("dataId", beanName);
        beanDefinitionBuilder.addPropertyValue("configFileLocation", configFile);
        BeanDefinition lastBeanDefinition = beanDefinitionBuilder.getRawBeanDefinition();
        lastBeanDefinition.setLazyInit(true);
        beanFactory.registerBeanDefinition(beanName, lastBeanDefinition);
        return beanFactory.getBean(beanName, DynamicDataSource.class);
    }

    /**
     * 数据库读写分离 SqlSessionFactory <bean id="writeSqlSessionFactory" class=
     * "org.mybatis.spring.SqlSessionFactoryBean">
     * <property name="dataSource" ref="mysql_dataSoruce_main" />
     * <property name="configLocation">
     * <value>classpath:sqlmapper/sqlmap\-config.xml</value> </property>
     * <property name="mapperLocations"> <list>
     * <value>classpath:sqlmapper/sqlmap\/**\/*.xml</value> </list> </property>
     * <property name="typeAliasesPackage" value="xxxx.model"/> </bean>
     *
     * <bean id="readSqlSessionFactory" class=
     * "org.mybatis.spring.SqlSessionFactoryBean">
     * <property name="dataSource" ref="mysql_dataSoruce_read" />
     * <property name="configLocation">
     * <value>classpath:sqlmapper/sqlmap-config.xml</value> </property>
     * <property name="mapperLocations"> <list>
     * <value>classpath:sqlmapper/sqlmap\/**\/*.xml</value> </list> </property>
     * <property name="typeAliasesPackage" value="xxxx.model"/> </bean>
     * 
     * <bean id="sqlSessionTemplate" class=
     * "com.mars.frame.mybatis.ReadWriteSqlSessionTemplate">
     * <constructor-arg ref="writeSqlSessionFactory" />
     * <property name="targetSqlSessionFactorys"> <map>
     * <entry value-ref="writeSqlSessionFactory" key="write"/>
     * <entry value-ref="readSqlSessionFactory" key="read"/> </map> </property>
     * </bean>
     * 
     * <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     * <property name="basePackage" value="xxx.mapper"/>
     * <property name="sqlSessionTemplateBeanName" value="sqlSessionTemplate" />
     * </bean>
     * 
     */
    @Bean
    public DynamicMapperScanAspect mapperScanAspect() {
        DynamicMapperScanAspect mapperScanAspect = new DynamicMapperScanAspect();
        return mapperScanAspect;
    }
}
